package com.google.code.jtasks;

import java.util.Set;

import junit.framework.Assert;

import org.junit.Test;

import com.google.code.jtasks.adapters.ITaskSystemAdapter;
import com.google.code.jtasks.connection.TaskRepoConnectionSettings;
import com.google.code.jtasks.manager.ITaskRepoManager;
import com.google.code.jtasks.manager.ITaskRepoManagerFactory;
import com.google.code.jtasks.manager.TaskRepoManagerFactoryImpl;
import com.google.code.jtasks.manager.cache.IQueryCache;
import com.google.code.jtasks.manager.cache.MockQueryCache;
import com.google.code.jtasks.model.IMappedTask;
import com.google.code.jtasks.model.MockMappedTask;
import com.google.code.jtasks.model.UnsupportedTaskPropertyException;
import com.google.code.jtasks.model.IMappedTask.TaskField;
import com.google.code.jtasks.query.TaskQuery;
import com.google.code.jtasks.query.TaskQueryResult;

/**
 * This abstract class can be used to test jtask API implementations for specific task systems. It should be
 * extended for each such implementation and run as part of the project's automated integration tests.
 */
public abstract class TaskSystemIntegrationTester
{
	private static ITaskRepoManagerFactory taskManagerFactory;

	/**
	 * This test checks to ensure that the {@link ITaskSystemAdapter#runQuery(TaskQuery)} works correctly for
	 * a {@link TaskQuery} filtering by a single {@link TaskField#ID}.
	 */
	@Test
	public final void idQuery_single()
	{
		ITaskRepoManager taskManager = createTaskManager();

		TaskQuery bugQuery = taskManager.createQuery().filterBy(TaskField.ID, getSampleTask1().getId());
		TaskQueryResult bug1Result = taskManager.query(bugQuery);
		IMappedTask task1 = bug1Result.getTask();
		Assert.assertNotNull("task not found", task1);
		Assert.assertEquals("task ID mismatch", getSampleTask1().getId(), task1.getId());
		Assert.assertEquals("task summary mismatch", getSampleTask1().getSummary(), task1.getSummary());
	}

	/**
	 * This test checks to ensure that the {@link ITaskSystemAdapter#runQuery(TaskQuery)} works correctly for
	 * a {@link TaskQuery} filtering by two {@link TaskField#ID}s.
	 */
	@Test
	public final void idQuery_multiple()
	{
		ITaskRepoManager taskManager = createTaskManager();

		TaskQuery bugsQuery = taskManager.createQuery().filterBy(TaskField.ID, getSampleTask1().getId(),
				getSampleTask2().getId());
		TaskQueryResult bugsResult = taskManager.query(bugsQuery);
		Assert.assertEquals("wrong number of results found", 2, bugsResult.getTasks().size());

		IMappedTask task1 = bugsResult.getTask(getSampleTask1().getId());
		Assert.assertNotNull("task not found", task1);
		Assert.assertEquals("task summary mismatch", getSampleTask1().getSummary(), task1.getSummary());

		IMappedTask task2 = bugsResult.getTask(getSampleTask2().getId());
		Assert.assertNotNull("task not found", task2);
		Assert.assertEquals("task summary mismatch", getSampleTask2().getSummary(), task2.getSummary());
	}

	/**
	 * Ensures that the {@link IMappedTask}s correctly handle optional/required {@link TaskField}s.
	 * 
	 * @see TaskField#isOptional()
	 * @see ITaskSystemAdapter#getUnsupportedTaskFields()
	 */
	@Test
	public final void requiredVsOptionalFields()
	{
		ITaskRepoManager taskManager = createTaskManager();

		// Run a query for a single task
		TaskQuery bugQuery = taskManager.createQuery().filterBy(TaskField.ID, getSampleTask1().getId());
		TaskQueryResult bug1Result = taskManager.query(bugQuery);
		IMappedTask task1 = bug1Result.getTask();
		Assert.assertNotNull("task not found", task1);

		ITaskSystemAdapter<?, ?> taskSystemAdapter = bugQuery.getConnectionSettings().getAdapter();
		Set<TaskField> unsupportedTaskFields = taskSystemAdapter.getUnsupportedTaskFields();
		for (TaskField field : TaskField.values())
		{
			if (unsupportedTaskFields.contains(field))
			{
				Assert
						.assertTrue(field + " is listed as unsupported but is not optional", field
								.isOptional());

				boolean unsupportedFieldExceptionThrown = false;
				try
				{
					field.getValue(task1);
				}
				catch (UnsupportedTaskPropertyException e)
				{
					unsupportedFieldExceptionThrown = true;
				}
				Assert.assertTrue(field + " is optional but did not throw an exception",
						unsupportedFieldExceptionThrown);
			}
			else
			{
				// Just making sure that this does not throw an exception:
				field.getValue(task1);
			}
		}
	}

	/**
	 * <p>
	 * Returns a {@link MockMappedTask} to be used in the tests, which must correspond to an actual task in
	 * the task system the integration tests are running against. This task must be distinct from the one
	 * returned by {@link #getSampleTask2()}. The only fields that are required to be present in the
	 * {@link MockMappedTask} are:
	 * </p>
	 * <ul>
	 * <li>{@link TaskField#ID}</li>
	 * <li>{@link TaskField#SUMMARY}</li>
	 * </ul>
	 * 
	 * @return a {@link MockMappedTask} to be used in the tests, which must correspond to an actual task in
	 *         the task system the integration tests are running against
	 */
	protected abstract MockMappedTask getSampleTask1();

	/**
	 * <p>
	 * Returns a {@link MockMappedTask} to be used in the tests, which must correspond to an actual task in
	 * the task system the integration tests are running against. This task must be distinct from the one
	 * returned by {@link #getSampleTask1()}. The only fields that are required to be present in the
	 * {@link MockMappedTask} are:
	 * </p>
	 * <ul>
	 * <li>{@link TaskField#ID}</li>
	 * <li>{@link TaskField#SUMMARY}</li>
	 * </ul>
	 * 
	 * @return a {@link MockMappedTask} to be used in the tests, which must correspond to an actual task in
	 *         the task system the integration tests are running against
	 */
	protected abstract MockMappedTask getSampleTask2();

	/**
	 * Returns the {@link TaskRepoConnectionSettings} for the task repository that the integration tests will
	 * be run against.
	 * 
	 * @return the {@link TaskRepoConnectionSettings} for the task repository that the integration tests will
	 *         be run against
	 */
	protected abstract TaskRepoConnectionSettings getConnection();

	/**
	 * Creates a new {@link ITaskRepoManagerFactory} to use in the integration tests.
	 * 
	 * @return a new {@link ITaskRepoManagerFactory} to use in the integration tests
	 */
	private ITaskRepoManagerFactory createTaskManagerFactory()
	{
		IQueryCache cache = new MockQueryCache();

		ITaskRepoManagerFactory taskManagerFactory = new TaskRepoManagerFactoryImpl(cache);
		taskManagerFactory.addConnection(getConnection());
		return taskManagerFactory;
	}

	/**
	 * Returns the {@link ITaskRepoManagerFactory} to use in the integration tests.
	 * 
	 * @return the {@link ITaskRepoManagerFactory} to use in the integration tests
	 */
	protected final ITaskRepoManagerFactory getTaskManagerFactory()
	{
		if (taskManagerFactory == null)
			taskManagerFactory = createTaskManagerFactory();

		return taskManagerFactory;
	}

	/**
	 * Returns a new instance of the {@link ITaskRepoManager} to use to access the task system that the
	 * integration tests are being run against.
	 * 
	 * @return a new instance of the {@link ITaskRepoManager} to use to access the task system that the
	 *         integration tests are being run against
	 */
	protected final ITaskRepoManager createTaskManager()
	{
		ITaskRepoManagerFactory taskManagerFactory = getTaskManagerFactory();
		return taskManagerFactory.createRepoManager(getConnection().getTaskRepoId());
	}
}
